/***************************************************************************************
* Copyright (c) 2014-2022 Zihao Yu, Nanjing University
*
* NEMU is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*          http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
*
* See the Mulan PSL v2 for more details.
***************************************************************************************/

#include "debug.h"
#include <common.h>
#include <device/map.h>
#include <SDL2/SDL.h>

enum {
	reg_freq, reg_channels, reg_samples, // 参数
	reg_sbuf_size,	// 流缓冲区的大小
	reg_init,		// 用于初始化
	reg_count,		// 当前流缓冲区已经使用的大小
	nr_reg
};
// subf(环形)缓冲区是一段MMIO空间, 用于存放来自程序的音频数据, 这些音频数据会在将来写入到SDL库中
static uint8_t *sbuf = NULL;
static uint32_t *audio_base = NULL;

static uint32_t rear = 0;

/**
 * stream 缓冲区由 SDL 维护, 可以看作 stream 无限长
 * len 是 stream 缓冲区的大小
 */
static inline void callback_audio_play(void *userdata, uint8_t *stream, int len) {
	uint32_t read_len = len;
	// 当前环形缓冲区 sbuf 已经使用的大小
	uint32_t count = audio_base[reg_count];	
	// 当前环形缓冲区 sbuf 的数据不够 SDL 一次性读取
	if (count < len) read_len = count;
	
	// 读环形缓冲区 sbuf 不需要折回
	if (rear + read_len < CONFIG_SB_SIZE) {
		// 拷贝数据
		memcpy(stream, sbuf+rear, read_len);
		// 更新队尾
		rear += read_len;
	}
	// 读 sbuf 环形缓冲区需要折回
	else {
		// 拷贝数据
		uint32_t first = CONFIG_SB_SIZE - rear;
		memcpy(stream, sbuf+rear, first);
		memcpy(stream+first, sbuf, read_len-first);
		// 更新队尾
		rear = read_len - first;
	}
	// 更新 reg_count 寄存器
	audio_base[reg_count] -= read_len;
	// 把 SDL 提供的缓冲区 stream 剩余的部分清零, 以避免把一些垃圾数据当做音频, 从而产生噪音.
	if (len > read_len) memset(stream + read_len, 0, len - read_len);
}

static void audio_io_handler(uint32_t offset, int len, bool is_write) {
	// 写 init 寄存器, 对 SDL 的音频子系统进行初始化
	// 读取其他寄存器, 不会执行下面的逻辑
	if (offset == reg_init * sizeof(uint32_t) && len == 4 && is_write) {
		SDL_AudioSpec s = {};
		s.freq = audio_base[reg_freq];
		s.format = AUDIO_S16SYS;
		s.channels = audio_base[reg_channels];
		s.samples = audio_base[reg_samples];
		s.callback = callback_audio_play;
		s.userdata = NULL;
		
		rear = 0;
		audio_base[reg_count] = 0;
		if (SDL_InitSubSystem(SDL_INIT_AUDIO) != 0) {
			Log("Failed to initialize audio subsystem");
			return;
		}
		SDL_OpenAudio(&s, NULL);
		SDL_PauseAudio(0);
	}
}

void init_audio() {
	uint32_t space_size = sizeof(uint32_t) * nr_reg;
	audio_base = (uint32_t *)new_space(space_size);
	audio_base[reg_sbuf_size] = CONFIG_SB_SIZE;
#ifdef CONFIG_HAS_PORT_IO
	add_pio_map ("audio", CONFIG_AUDIO_CTL_PORT, audio_base, space_size, audio_io_handler);
#else
	add_mmio_map("audio", CONFIG_AUDIO_CTL_MMIO, audio_base, space_size, audio_io_handler);
#endif

	sbuf = (uint8_t *)new_space(CONFIG_SB_SIZE);
	add_mmio_map("audio-sbuf", CONFIG_SB_ADDR, sbuf, CONFIG_SB_SIZE, NULL);
}
